﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TransportAbstractionLibrary.Exceptions;

namespace TransportAbstractionLibrary.Layer
{
    public sealed class PackageLayer : IProtocolLowerLayer, IProtocolUpperLayer
    {
        bool initialized;
        Guid id = new Guid("29C2858A-2E70-4773-B007-6F5C11FA96A2");
        IProtocolUpperLayer upperLayer;
        IProtocolLowerLayer lowerLayer;
        int maxPacketSize;

        public PackageLayer(int maxPacketSize)
        {
            this.maxPacketSize = maxPacketSize;
        }

        #region IProtocolLowerLayer Members

        public void Bind(IProtocolUpperLayer upperLayer)
        {
            if (this.upperLayer != null)
            {
                OnException(new AlreadyBoundException(this));
                return;
            }
            this.upperLayer = upperLayer;
            upperLayer.Bind(this);
        }

        public void Send(byte[] data)
        {
            if (data.Length > maxPacketSize)
            {
                OnException(new TALException(this, "To big packet!"));
                return;
            }
            byte[] buffer = new byte[data.Length + 4];
            byte[] lengthBytes = BitConverter.GetBytes(data.Length);
            Buffer.BlockCopy(lengthBytes, 0, buffer, 0, 4);
            Buffer.BlockCopy(data, 0, buffer, 4, data.Length);
            lowerLayer.Send(buffer);
        }

        public IProtocolUpperLayer UpperLayer
        {
            get { return upperLayer; }
        }

        #endregion

        #region IProtocolUpperLayer Members

        bool hasLength = false;
        int length;
        int bufferPosition;
        byte[] buffer = null;
        byte[] lengthBuffer = new byte[4];

        void IProtocolUpperLayer.Bind(IProtocolLowerLayer lowerLayer)
        {
            if ((lowerLayer.LayerType & (LayerType.Order | LayerType.Integrity)) != (LayerType.Order | LayerType.Integrity))
            {
                throw new InvalidLayerOrderException(this, "The PackageLayer requiers a Order and Integrity layer to be between it and the Transport layer");
            }
            this.lowerLayer = lowerLayer;
        }

        void IProtocolUpperLayer.Receive(byte[] data)
        {
            int dataPosition = 0;

            while (true)
            {
                if (!hasLength)
                {
                    //
                    int bytesToCopy = Math.Min(4, data.Length - dataPosition);
                    Buffer.BlockCopy(data, dataPosition, lengthBuffer, bufferPosition, bytesToCopy);
                    dataPosition += bytesToCopy;
                    bufferPosition += bytesToCopy;
                    if (bufferPosition == 4)
                    {
                        //yay we got the whole length!
                        bufferPosition = 0;
                        hasLength = true;
                        length = BitConverter.ToInt32(lengthBuffer, 0);
                        buffer = new byte[length];
                    }
                    else
                    {
                        return;
                    }
                }

                if (hasLength)
                {

                    int bytesToCopy = Math.Min(length - bufferPosition, data.Length - dataPosition);
                    Buffer.BlockCopy(data, dataPosition, buffer, bufferPosition, bytesToCopy);
                    dataPosition += bytesToCopy;
                    bufferPosition += bytesToCopy;
                    if (bufferPosition == length)
                    {
                        if (initialized)
                        {
                            upperLayer.Receive(buffer);
                        }
                        else
                        {
                            if (BinaryHelper.CompareByte(id.ToByteArray(), buffer))
                            {
                                initialized = true;
                                upperLayer.Initialize();
                            }
                            else
                            {
                                OnException(new InvalidLayerOrderException(this));
                                return;
                            }
                        }
                        //yay we got all data!
                        hasLength = false;
                        bufferPosition = 0;
                        buffer = null;
                    }
                    else
                    {
                        return;
                    }
                }
            }
        }

        public IProtocolLowerLayer LowerLayer
        {
            get { return lowerLayer; }
        }

        void IProtocolUpperLayer.ConnectionClosed()
        {
            upperLayer.ConnectionClosed();
        }

        public void OnException(TALException talException)
        {
            upperLayer.OnException(talException);
        }
        #endregion

        #region IProtocolBase Members

        public Guid Id
        {
            get
            {
                return id;
            }
        }

        public LayerType LayerType
        {
            get 
            {
                if (lowerLayer != null)
                {
                    return lowerLayer.LayerType | LayerType.Package;
                }
                else
                {
                    return LayerType.Package;
                }
            }
        }

        void IProtocolBase.Initialize()
        {
            //We should send some data and validate that its the same layer at this level!

            //upperLayer.Initialize();
            if (lowerLayer.SuppressInitialization)
            {
                initialized = true;
                upperLayer.Initialize();
            }
            else
            {
                Send(id.ToByteArray());
            }
        }

        bool IProtocolBase.SuppressInitialization
        {
            get { return lowerLayer.SuppressInitialization; }
        }


        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            lowerLayer.Dispose();
        }

        #endregion


    }
}
